#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"flask send-bills --month 202509"

import requests
import re
import json
from decimal import Decimal
from datetime import datetime, timedelta, date
from bs4 import BeautifulSoup
from ldap3 import Server, Connection, ALL, SUBTREE

import click
from flask import current_app
from flask_mail import Message
from app import app, mail



# --- 4. SCOW 配置 ---
SCOW_CONFIG = {
    'login_url': 'http://scow.saids.hpc.gleamoe.com/auth/public/auth?callbackUrl=http%3A%2F%2Fscow.saids.hpc.gleamoe.com%2Fapi%2Fauth%2Fcallback',
    'username_field': 'username',
    'password_field': 'password',
    'bill_url': 'http://scow.saids.hpc.gleamoe.com/mis/tenant/finance/bills',
    'api_url_template': 'http://scow.saids.hpc.gleamoe.com/mis/api/finance/bill?page=1&pageSize=500&searchType=selfTenant&termStart={month}&termEnd={month}&type=1',
    'api_details_url_template': 'http://scow.saids.hpc.gleamoe.com/mis/api/finance/userBill?accountBillIds={id}&accountName={username}',
    'username': 'scowadmin',
    'password': 'Saids_scowadmin'
}


# --- 5. 核心功能函数 ---

def fetch_scow_bills(session, billing_month):
    """
    [V11 - LDAP 修复版]
    抓取 API 1 (主列表) 和 API 2 (详情)，
    并正确区分 'accountName' (显示用) 和 'accountOwnerId' (LDAP查询用)。
    """
    try:
        api_url_template = SCOW_CONFIG['api_url_template']
        main_api_url = api_url_template.format(month=billing_month)
        api_details_template = SCOW_CONFIG['api_details_url_template']
    except KeyError as e:
        click.echo(f"错误: SCOW_CONFIG 中缺少配置 {e}。", err=True)
        return None, None
        
    click.echo(f"--- 阶段 1: 正在调用主列表 API ---")
    click.echo(f"API URL: {main_api_url}")

    try:
        response = session.get(main_api_url)
        response.raise_for_status()
        data = response.json()
        bill_list = data.get('bills', [])
        if not bill_list:
            click.echo("API 1 成功，但未返回任何账单数据。")
            return [], billing_month

        click.echo(f"API 1 成功，获取到 {len(bill_list)} 条总账单。")

        click.echo("\n--- 阶段 2: 开始抓取作业详情 (仅限有费用的用户) ---")
        
        billing_data = [] # 最终返回的数据
        
        for item in bill_list:
            # 1. 计算总金额 (API 1)
            yuan = item.get('amount', {}).get('yuan', 0)
            decimal = item.get('amount', {}).get('decimalPlace', 0)
            total_amount = Decimal(yuan) + (Decimal(decimal) / Decimal(10000))

            # 2. 只处理有费用的用户
            if total_amount > 0:
                # [!! V11 关键修复 !!]
                accountName_display = item.get('accountName')    # (例如 'hexiangnan', 用于显示)
                username_ldap = item.get('accountOwnerId') # (例如 'hexn', 用于LDAP查询)
                bill_id = item.get('id')
                
                # 从 API 1 提取总作业费用
                job_cost_str = item.get('details', {}).get('作业费用', '0')
                job_cost_cleaned = re.sub(r'[^\d.]', '', job_cost_str)
                if not job_cost_cleaned: job_cost_cleaned = '0'
                total_job_cost = Decimal(job_cost_cleaned)

                if not username_ldap or not accountName_display or not bill_id:
                    click.echo(f"警告：跳过一条有费用的记录，缺少 'accountOwnerId', 'accountName' 或 'id'。")
                    continue
                        
                click.echo(f"--- 正在为账户 [{accountName_display}] (LDAP ID: {username_ldap}) 抓取详情...")
                
                details_list = [] # 存储作业明细
                
                try:
                    # 3. 构建并调用详情 API (API 2)
                    # (使用 accountName_display (hexiangnan) 作为 API 2 的参数)
                    details_url = api_details_template.format(id=bill_id, username=accountName_display) 
                    details_response = session.get(details_url)
                    details_response.raise_for_status()
                    details_json = details_response.json()

                    # 4. 解析详情 JSON (userBills)
                    for user_bill in details_json.get('userBills', []):
                        user_id = user_bill.get('userId') # (例如 fancx)
                        
                        detail_yuan = user_bill.get('amount', {}).get('yuan', 0)
                        detail_decimal = user_bill.get('amount', {}).get('decimalPlace', 0)
                        detail_cost = Decimal(detail_yuan) + (Decimal(detail_decimal) / Decimal(10000))
                        
                        if user_id:
                            details_list.append({
                                'userId': user_id,
                                'cost': detail_cost
                            })
                    
                    click.echo(f"成功抓取到 {len(details_list)} 条作业明细。")

                except requests.RequestException as e:
                    click.echo(f"警告: 为账户 [{accountName_display}] 抓取详情失败: {e}", err=True)
                    click.echo("邮件将只包含总金额，不含作业明细。")
                except Exception as e:
                    click.echo(f"警告: 处理账户 [{accountName_display}] 的详情时出错: {e}", err=True)

                # 5. 将所有数据汇总
                billing_data.append({
                    'username': username_ldap,            # [V11] LDAP 查询用 (hexn)
                    'accountName_display': accountName_display, # [V11] 邮件显示用 (hexiangnan)
                    'total_amount': total_amount,
                    'job_cost': total_job_cost,
                    'details': details_list
                })

        click.echo("="*30)
        click.echo(f"\n✅ 阶段 2 完成。共找到 {len(billing_data)} 个有费用的用户。")
        
        return billing_data, billing_month

    except requests.RequestException as e:
        click.echo(f"API (主列表) 调用失败: {e}", err=True)
        return None, None
    except json.JSONDecodeError:
        click.echo("错误：API (主列表) 响应不是有效的JSON。")
        click.echo(f"原始响应: {response.text[:200]}...")
        return None, None


def get_email_from_ldap(ldap_conn, username):
    """
    [!! GEMINI 修改 !!]
    (现在将使用 'hexn' 来查询)
    返回一个包含 'email', 'sn' (姓), 'givenName' (名) 的字典。
    """
    try:
        LDAP_BASE_DN = current_app.config['LDAP_BASE_DN']
        LDAP_USER_OU = current_app.config.get('LDAP_USER_OU', 'ou=People')
        LDAP_USER_SEARCH_BASE = f"{LDAP_USER_OU},{LDAP_BASE_DN}"
    except KeyError as e:
        click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
        return None

    search_filter = f'(uid={username})'
    # [!! GEMINI 修改 !!] 增加 'sn' (姓) 和 'givenName' (名)
    attributes_to_fetch = ['mail', 'sn', 'givenName']
    
    try:
        ldap_conn.search(
            search_base=LDAP_USER_SEARCH_BASE,
            search_filter=search_filter,
            search_scope=SUBTREE,
            attributes=attributes_to_fetch
        )
        
        if ldap_conn.response and len(ldap_conn.response) > 0:
            attrs = ldap_conn.response[0]['attributes']
            email_list = attrs.get('mail')
            
            if email_list:
                email = email_list[0]
                # [!! GEMINI 修改 !!] 提取 sn 和 givenName (它们也可能是列表)
                sn_list = attrs.get('sn')
                givenName_list = attrs.get('givenName')
                
                ldap_info = {
                    'email': email,
                    'sn': sn_list[0] if sn_list else None,
                    'givenName': givenName_list[0] if givenName_list else None
                }
                
                click.echo(f"LDAP 查找成功: {username} -> {ldap_info}")
                return ldap_info
            
        click.echo(f"LDAP中未找到用户 {username} 的邮箱 (mail) 属性。", err=True)
        return None
        
    except Exception as e:
        click.echo(f"LDAP查询用户 {username} 时出错: {e}", err=True)
        return None

   
# def send_billing_email(ldap_info, bill_data, billing_period):
#     """
#     [!! GEMINI 修改 !!]
#     签名已更改: 接收 'ldap_info' (字典) 而不是 'user_email'。
#     """
#     try:
#         sender_email = current_app.config['MAIL_USERNAME']
#         sender_name = current_app.config.get('MAIL_SENDER_NAME', '算力平台管理员')
#     except KeyError as e:
#         click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
#         return

#     # [!! GEMINI 修改 !!] 从 ldap_info 提取信息
#     user_email = ldap_info['email']
#     last_name = ldap_info.get('sn')
#     first_name = ldap_info.get('givenName')
    
#     # [!! GEMINI 修改 !!] 动态构建问候语
#     username_display = bill_data['accountName_display'] # (例如 hexiangnan)
    
#     if last_name and first_name:
#         # 成功获取到 LDAP 姓名
#         greeting_name = f"{last_name}{first_name}老师" # 例如: 何向南老师
#     else:
#         # Fallback: 使用 SCOW 的 accountName
#         greeting_name = username_display

#     msg = Message(
#         subject=f"【HPC平台】{billing_period} 账期账单通知 (账户: {username_display})",
#         sender=f"{sender_name} <{sender_email}>",
#         recipients=[user_email] # [!! GEMINI 修改 !!] 使用从 ldap_info 提取的 email
#     )

#     login_url = SCOW_CONFIG['bill_url']
    
#     # --- [!! V10 动态生成详情表格 !!] ---
#     details_html = ""
#     if 'details' in bill_data and bill_data['details']:
#         details_html = """
#         <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #10b981; padding-left: 10px;">
#             作业明细 (按用户ID)
#         </h3>
#         <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #ffffff; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
#             <thead style="background-color: #f8fafb;">
#                 <tr>
#                     <th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">用户ID</th>
#                     <th style="text-align: right; padding: 8px; border-bottom: 1px solid #ddd;">小计 (元)</th>
#                 </tr>
#             </thead>
#             <tbody>
#         """
#         for detail_item in bill_data['details']:
#             details_html += f"""
#                 <tr>
#                     <td style="padding: 8px; border-bottom: 1px solid #e8e8e8;">{detail_item['userId']}</td>
#                     <td style="text-align: right; padding: 8px; border-bottom: 1px solid #e8e8e8;">{detail_item['cost']:.2f}</td>
#                 </tr>
#             """
#         details_html += "</tbody></table>"
#     # --- [!! 动态表格结束 !!] ---

    
#     # (V11 - 最终HTML模板)
#     msg.html = f"""
#     <!DOCTYPE html>
#     <html lang="zh-CN">
#     <head><meta charset="UTF-8"><title>账单通知</title></head>
#     <body style="margin: 0; padding: 0; background-color: #f4f7f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
#         <table width="100%" border="0" cellpadding="0" cellspacing="0" style="background-color: #f4f7f6;">
#             <tr>
#                 <td align="center" style="padding: 20px 0;">
#                     <table width="600" border="0" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
#                         <tr>
#                             <td align="center" style="padding: 30px 0; border-bottom: 1px solid #e8e8e8;">
#                                 <img src="cid:logo_image" alt="平台Logo" width="80" style="display: block;">
#                                 <h1 style="color: #0a2540; margin-top: 15px; font-size: 24px;">HPC 平台账单通知</h1>
#                             </td>
#                         </tr>
#                         <tr>
#                             <td style="padding: 30px 40px;">
#                                 <p style="color: #333; font-size: 16px; line-height: 1.6;">您好， {greeting_name}！</p>
#                                 <p style="color: #333; font-size: 16px; line-height: 1.6;">您在 {billing_period} 账期的账单已生成。明细如下：</p>
                                
#                                 <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #134790; padding-left: 10px;">
#                                     账单总览
#                                 </h3>
#                                 <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
#                                     <tr><td width="80" style="color: #555;">账&nbsp; 期:</td><td style="color: #111;">{billing_period}</td></tr>
#                                     <tr><td width="80" style="color: #555;">账户名:</td><td style="color: #111;">{username_display}</td></tr>
#                                     <tr><td style="color: #555;">作业费用:</td><td style="color: #111;">{bill_data['job_cost']:.2f}</td></tr>
#                                     <tr><td style="color: #555;"><b>总金额:</b></td><td style="color: #d9534f; font-weight: bold;">{bill_data['total_amount']:.2f}</td></tr>
#                                 </table>
                                
#                                 {details_html}
                                
#                                 <table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding: 20px 0;">
#                                     <a href="{login_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;">登录平台查看</a>
#                                 </td></tr></table>
#                             </td>
#                         </tr>
#                         <tr>
#                             <td style="padding: 30px 40px; border-top: 1px solid #e8e8e8; text-align: center; color: #999; font-size: 12px;">
#                                 <p>此邮件为系统自动发送，请勿回复。</p>
#                                 <p>人工智能与数据科学学院算力中心</p>
#                             </td>
#                         </tr>
#                     </table>
#                 </td>
#             </tr>
#         </table>
#     </body>
#     </html>
#     """
    
#     # --- 嵌入 Logo 图片 ---
#     try:
#         logo_path = 'static/images/logo.png' 
#         with current_app.open_resource(logo_path, 'rb') as fp:
#             msg.attach(filename='logo.png', content_type='image/png', data=fp.read(), disposition='inline', headers={'Content-ID': '<logo_image>'})
#     except Exception as e:
#         click.echo(f"警告: 无法嵌入Logo图片 '{logo_path}': {e}", err=True)
#     # --- 发送邮件 ---
#     try:
#         mail.send(msg)
#         click.echo(f"已成功发送账单邮件至 {greeting_name} ({user_email})")
#     except Exception as e:
#         click.echo(f"发送邮件至 {greeting_name} ({user_email}) 失败: {e}", err=True)



def send_billing_email(ldap_info, bill_data, billing_period):
    """
    [!! GEMINI 修改 V3 (带歉意) !!]
    签名已更改: 接收 'ldap_info' (字典) 而不是 'user_email'。
    新增逻辑: 格式化账期 (202510 -> 2025年10月)
    新增逻辑: 在邮件正文中添加致歉说明。
    """
    try:
        sender_email = current_app.config['MAIL_USERNAME']
        sender_name = current_app.config.get('MAIL_SENDER_NAME', '算力平台管理员')
    except KeyError as e:
        click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
        return

    # [!! GEMINI 修改 V3 !!] 格式化账期 (不增加月份)
    try:
        # 1. 将 "202510" 字符串解析为日期对象 (2025-10-01)
        display_date = datetime.strptime(billing_period, '%Y%m').date()
        # 2. 创建 "2025年10月" 格式
        display_period_human = display_date.strftime('%Y年%m月')
    except Exception as e:
        click.echo(f"警告: 格式化账期 ({billing_period}) 时出错: {e}", err=True)
        # 回退
        display_period_human = f"{billing_period[0:4]}年{billing_period[4:6]}月"

    # [!! GEMINI 修改 !!] 从 ldap_info 提取信息
    user_email = ldap_info['email']
    last_name = ldap_info.get('sn')
    first_name = ldap_info.get('givenName')
    
    # [!! GEMINI 修改 !!] 动态构建问候语
    username_display = bill_data['accountName_display'] # (例如 hexiangnan)
    
    if last_name and first_name:
        # 成功获取到 LDAP 姓名
        greeting_name = f"{last_name}{first_name}老师" # 例如: 何向南老师
    else:
        # Fallback: 使用 SCOW 的 accountName
        greeting_name = username_display

    msg = Message(
        # [!! GEMINI 修改 V3 !!] 主题使用格式化后的账期，并添加 "更正"
        subject=f"【HPC平台】账单更正 - {display_period_human} 账期账单通知 (账户: {username_display})",
        sender=f"{sender_name} <{sender_email}>",
        recipients=[user_email] 
    )

    login_url = SCOW_CONFIG['bill_url']
    
    # --- [!! V10 动态生成详情表格 !!] ---
    # (此部分逻辑不变)
    details_html = ""
    if 'details' in bill_data and bill_data['details']:
        details_html = """
        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #10b981; padding-left: 10px;">
            作业明细 (按用户ID)
        </h3>
        <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #ffffff; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
            <thead style="background-color: #f8fafb;">
                <tr>
                    <th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">用户ID</th>
                    <th style="text-align: right; padding: 8px; border-bottom: 1px solid #ddd;">小计 (元)</th>
                </tr>
            </thead>
            <tbody>
        """
        for detail_item in bill_data['details']:
            details_html += f"""
                <tr>
                    <td style="padding: 8px; border-bottom: 1px solid #e8e8e8;">{detail_item['userId']}</td>
                    <td style="text-align: right; padding: 8px; border-bottom: 1px solid #e8e8e8;">{detail_item['cost']:.2f}</td>
                </tr>
            """
        details_html += "</tbody></table>"
    # --- [!! 动态表格结束 !!] ---

    
    # (V11 - 最终HTML模板)
    msg.html = f"""
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head><meta charset="UTF-8"><title>账单通知 (更正)</title></head>
    <body style="margin: 0; padding: 0; background-color: #f4f7f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
        <table width="100%" border="0" cellpadding="0" cellspacing="0" style="background-color: #f4f7f6;">
            <tr>
                <td align="center" style="padding: 20px 0;">
                    <table width="600" border="0" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
                        <tr>
                            <td align="center" style="padding: 30px 0; border-bottom: 1px solid #e8e8e8;">
                                <img src="cid:logo_image" alt="平台Logo" width="80" style="display: block;">
                                <h1 style="color: #0a2540; margin-top: 15px; font-size: 24px;">HPC 平台账单通知 (更正)</h1>
                            </td>
                        </tr>
                        <tr>
                            <td style="padding: 30px 40px;">
                                <p style="color: #333; font-size: 16px; line-height: 1.6;">您好， {greeting_name}！</p>
                                
                                <p style="color: #d9534f; font-size: 16px; line-height: 1.6;"><b>刚才发送的邮件有误，非常抱歉，下面才是您 {display_period_human} 账单情况。</b></p>
                                
                                <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #134790; padding-left: 10px;">
                                    账单总览
                                </h3>
                                <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
                                    <tr><td width="80" style="color: #555;">账&nbsp; 期:</td><td style="color: #111;">{display_period_human}</td></tr>
                                    <tr><td width="80" style="color: #555;">账户名:</td><td style="color: #111;">{username_display}</td></tr>
                                    <tr><td style="color: #555;">作业费用:</td><td style="color: #111;">{bill_data['job_cost']:.2f}</td></tr>
                                    <tr><td style="color: #555;"><b>总金额:</b></td><td style="color: #d9534f; font-weight: bold;">{bill_data['total_amount']:.2f}</td></tr>
                                </table>
                                
                                {details_html}
                                
                                <table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding: 20px 0;">
                                    <a href="{login_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;">登录平台查看</a>
                                </td></tr></table>
                            </td>
                        </tr>
                        <tr>
                            <td style="padding: 30px 40px; border-top: 1px solid #e8e8e8; text-align: center; color: #999; font-size: 12px;">
                                <p>此邮件为系统自动发送，请勿回复。</p>
                                <p>人工智能与数据科学学院算力中心</p>
                            </td>
                        </tr>
                    </table>
                </td>
            </tr>
        </table>
    </body>
    </html>
    """
    
    # --- 嵌入 Logo 图片 ---
    try:
        logo_path = 'static/images/logo.png' 
        with current_app.open_resource(logo_path, 'rb') as fp:
            msg.attach(filename='logo.png', content_type='image/png', data=fp.read(), disposition='inline', headers={'Content-ID': '<logo_image>'})
    except Exception as e:
        click.echo(f"警告: 无法嵌入Logo图片 '{logo_path}': {e}", err=True)
    # --- 发送邮件 ---
    try:
        mail.send(msg)
        click.echo(f"已成功发送 [更正] 账单邮件至 {greeting_name} ({user_email})")
    except Exception as e:
        click.echo(f"发送 [更正] 账单邮件至 {greeting_name} ({user_email}) 失败: {e}", err=True)


# --- 6. 注册为 Flask CLI 命令 (V11 - 生产版) ---

@app.cli.command("send-bills")
@click.option('--test-user', default=None, help='指定一个测试用户名 (如 ljt1) 进行全流程测试。')
@click.option('--month', default=None, help='指定账期 (格式: YYYYMM, 例如: 202509)。默认为上个月。')
def send_bills_command(test_user, month):
    """
    登录SCOW, 调用API抓取账单, 并通过LDAP查询邮箱, 最后发送账单。
    """
    click.echo("--- 开始执行账单通知任务 (V11 - 生产版) ---")

    # 1. 登录 SCOW
    session = requests.Session()
    login_url = SCOW_CONFIG['login_url']
    try:
        click.echo(f"正在访问 (GET) {login_url} 以获取 cookies...")
        get_resp = session.get(login_url)
        get_resp.raise_for_status()
    except requests.RequestException as e:
        click.echo(f"访问 (GET) 登录页面失败: {e}", err=True)
        return
    try:
        click.echo("正在解析登录表单以构建动态 payload...")
        soup = BeautifulSoup(get_resp.text, 'html.parser')
        login_form = soup.find('form', id='loginForm')
        if not login_form:
            click.echo("错误: 未能在登录页面上找到 id='loginForm' 的表单。", err=True)
            return
        inputs = login_form.find_all('input')
        login_payload = {}
        for input_tag in inputs:
            name = input_tag.get('name')
            value = input_tag.get('value', '')
            if name: login_payload[name] = value
        login_payload[SCOW_CONFIG['username_field']] = SCOW_CONFIG['username']
        login_payload[SCOW_CONFIG['password_field']] = SCOW_CONFIG['password']
        debug_payload = {k: v for k, v in login_payload.items() if k != SCOW_CONFIG['password_field']}
        click.echo(f"动态 Payload 构建完成: {debug_payload}")
    except Exception as e:
        click.echo(f"解析登录表单时出错: {e}", err=True)
        return
    try:
        click.echo(f"正在登录 (POST) {login_url} ...")
        login_resp = session.post(login_url, data=login_payload)
        login_resp.raise_for_status()
        if "账号密码登录" in login_resp.text:
             click.echo("登录失败！服务器返回了登录页面。", err=True)
             click.echo("请立即核对 SCOW_CONFIG 中的 'username' 和 'password'。", err=True)
             return
        click.echo("登录 SCOW 成功。")
    except requests.RequestException as e:
        click.echo(f"登录 (POST) SCOW 失败: {e}", err=True)
        return

    # 2. 确定账期
    if month:
        billing_month_str = month
        click.echo(f"使用指定的账期: {billing_month_str}")
    else:
        today = date.today()
        first_day_current = today.replace(day=1)
        last_day_prev = first_day_current - timedelta(days=1)
        billing_month_str = last_day_prev.strftime('%Y%m')
        click.echo(f"未指定账期，自动使用上个月: {billing_month_str}")

    # 3. 抓取账单 (调用V11 生产版)
    bills, billing_period = fetch_scow_bills(session, billing_month_str)
    
    if bills is None:
        click.echo("获取账单失败。任务终止。")
        return
    
    # 4. [!! V11 测试用户注入逻辑 (已更新) !!]
    if test_user:
        click.echo(f"\n--- 启动 {test_user} 全流程测试 ---")
        ljt1_found = any(bill['username'] == test_user for bill in bills)
        if not ljt1_found:
            click.echo(f"用户 {test_user} 的费用为0或未在账单中。正在手动注入...")
            bills.append({ 
                'username': test_user,            # LDAP 查询用 (ljt1)
                'accountName_display': test_user, # 邮件显示用 (ljt1)
                'total_amount': Decimal('0.00'), 
                'job_cost': Decimal('0.00'), 
                'details': [] # 注入一个空的详情列表
            })
            click.echo(f"已成功注入 {test_user} (0元账单)。")
    
    if not bills:
        click.echo("没有需要通知的用户（费用>0），任务完成。")
        return

    # 5. 连接 LDAP
    try:
        LDAP_SERVER_URI = current_app.config['LDAP_SERVER']
        LDAP_BIND_DN = current_app.config['LDAP_BIND_DN']
        LDAP_BIND_PASSWORD = current_app.config['LDAP_BIND_PASSWORD']
        server = Server(LDAP_SERVER_URI, get_info=ALL)
        ldap_conn = Connection(server, LDAP_BIND_DN, LDAP_BIND_PASSWORD, auto_bind=True)
        click.echo("连接 LDAP 服务器成功。")
    except Exception as e:
        click.echo(f"连接 LDAP 失败: {e}", err=True)
        return

    # 6. 迭代处理并发送邮件 [!! GEMINI 修改 !!]
    click.echo("\n--- 开始处理账单并发送邮件 ---")
    for bill in bills:
        username_ldap = bill['username']            # (例如 hexn)
        username_display = bill['accountName_display'] # (例如 hexiangnan)
        
        click.echo(f"\n正在处理账户: {username_display} (LDAP ID: {username_ldap})")
        
        # [!! GEMINI 修改 !!] 现在返回的是一个字典
        ldap_info = get_email_from_ldap(ldap_conn, username_ldap)
        
        # [!! GEMINI 修改 !!] 检查字典和里面的 'email'
        if ldap_info and ldap_info.get('email'):
            # (调用 V11 邮件函数, 传入整个 ldap_info 字典)
            send_billing_email(ldap_info, bill, billing_period)
        else:
            click.echo(f"跳过账户 {username_display} (未找到邮箱或LDAP信息)。")

    # 7. 清理
    ldap_conn.unbind()
    click.echo("\n--- 所有任务已完成 ---")
    
# --- 7. [!! GEMINI 新增 !!] 用于测试姓名的专用CLI命令 ---

@app.cli.command("test-ldap-names")
@click.option('--month', default=None, help='指定账期 (格式: YYYYMM, 例如: 202509)。默认为上个月。')
def test_ldap_names_command(month):
    """
    [GEMINI 测试脚本]
    登录SCOW，抓取所有有费用的用户账单，然后仅从LDAP查询并打印他们的
    真实姓名 (sn + givenName)，不会发送任何邮件。
    """
    click.echo("--- 开始执行 [测试姓名] 任务 ---")
    click.echo("警告: 此脚本仅用于测试，不会发送任何邮件。")

    # 1. 登录 SCOW (复制自 send_bills_command)
    session = requests.Session()
    login_url = SCOW_CONFIG['login_url']
    try:
        click.echo(f"正在访问 (GET) {login_url} 以获取 cookies...")
        get_resp = session.get(login_url)
        get_resp.raise_for_status()
    except requests.RequestException as e:
        click.echo(f"访问 (GET) 登录页面失败: {e}", err=True)
        return
    try:
        click.echo("正在解析登录表单以构建动态 payload...")
        soup = BeautifulSoup(get_resp.text, 'html.parser')
        login_form = soup.find('form', id='loginForm')
        if not login_form:
            click.echo("错误: 未能在登录页面上找到 id='loginForm' 的表单。", err=True)
            return
        inputs = login_form.find_all('input')
        login_payload = {}
        for input_tag in inputs:
            name = input_tag.get('name')
            value = input_tag.get('value', '')
            if name: login_payload[name] = value
        login_payload[SCOW_CONFIG['username_field']] = SCOW_CONFIG['username']
        login_payload[SCOW_CONFIG['password_field']] = SCOW_CONFIG['password']
        click.echo("动态 Payload 构建完成 (密码已隐藏)。")
    except Exception as e:
        click.echo(f"解析登录表单时出错: {e}", err=True)
        return
    try:
        click.echo(f"正在登录 (POST) {login_url} ...")
        login_resp = session.post(login_url, data=login_payload)
        login_resp.raise_for_status()
        if "账号密码登录" in login_resp.text:
             click.echo("登录失败！服务器返回了登录页面。", err=True)
             return
        click.echo("登录 SCOW 成功。")
    except requests.RequestException as e:
        click.echo(f"登录 (POST) SCOW 失败: {e}", err=True)
        return

    # 2. 确定账期 (复制自 send_bills_command)
    if month:
        billing_month_str = month
        click.echo(f"使用指定的账期: {billing_month_str}")
    else:
        today = date.today()
        first_day_current = today.replace(day=1)
        last_day_prev = first_day_current - timedelta(days=1)
        billing_month_str = last_day_prev.strftime('%Y%m')
        click.echo(f"未指定账期，自动使用上个月: {billing_month_str}")

    # 3. 抓取账单 (调用V11 生产版)
    # (fetch_scow_bills 内部已经帮我们筛选了有费用的用户)
    bills, billing_period = fetch_scow_bills(session, billing_month_str)
    
    if bills is None:
        click.echo("获取账单失败。任务终止。")
        return
    
    if not bills:
        click.echo(f"账期 {billing_period} 内没有找到有费用的用户。")
        return
    
    click.echo(f"✅ 成功抓取 {len(bills)} 个有费用的用户账单。")

    # 5. 连接 LDAP (复制自 send_bills_command)
    try:
        LDAP_SERVER_URI = current_app.config['LDAP_SERVER']
        LDAP_BIND_DN = current_app.config['LDAP_BIND_DN']
        LDAP_BIND_PASSWORD = current_app.config['LDAP_BIND_PASSWORD']
        server = Server(LDAP_SERVER_URI, get_info=ALL)
        ldap_conn = Connection(server, LDAP_BIND_DN, LDAP_BIND_PASSWORD, auto_bind=True)
        click.echo("连接 LDAP 服务器成功。")
    except Exception as e:
        click.echo(f"连接 LDAP 失败: {e}", err=True)
        return

    # 6. [!! 核心测试逻辑 !!] 迭代处理并打印姓名
    click.echo("\n--- 开始查询有费用用户的真实姓名 ---")
    
    found_count = 0
    not_found_count = 0
    
    for bill in bills:
        username_ldap = bill['username']            # (例如 hexn)
        username_display = bill['accountName_display'] # (例如 hexiangnan)
        
        # 调用我们修改后的函数
        ldap_info = get_email_from_ldap(ldap_conn, username_ldap)
        
        if ldap_info and ldap_info.get('sn') and ldap_info.get('givenName'):
            last_name = ldap_info['sn']
            first_name = ldap_info['givenName']
            click.echo(f"  [成功] SCOW ID: {username_display:<15} (LDAP: {username_ldap:<8}) -> 真实姓名: {last_name}{first_name}")
            found_count += 1
        else:
            click.echo(f"  [失败] SCOW ID: {username_display:<15} (LDAP: {username_ldap:<8}) -> 未在LDAP中找到 'sn' 或 'givenName'。")
            not_found_count += 1

    # 7. 清理 (复制自 send_bills_command)
    ldap_conn.unbind()
    click.echo("\n--- 姓名查询测试完成 ---")
    click.echo(f"总计: {len(bills)} 个有费用的用户")
    click.echo(f"成功找到姓名: {found_count}")
    click.echo(f"未找到姓名: {not_found_count}")




# #!/usr/bin/env python3
# # -*- coding: utf-8 -*-

# # --- 1. Python 标准库和第三方库 ---
# import requests 
# import re
# import json
# from decimal import Decimal
# from datetime import datetime, timedelta, date
# from bs4 import BeautifulSoup
# from ldap3 import Server, Connection, ALL, SUBTREE

# # --- 2. Flask 和应用相关的 imports ---
# import click
# from flask import current_app
# from flask_mail import Message

# # --- 3. 从您的主 'app.py' 导入 app 和 mail 实例 ---
# try:
#     from app import app, mail
# except ImportError as e:
#     print("CRITICAL ERROR: 无法从 'app' 导入 'app' 和 'mail'。")
#     print("请确保此文件与您的主 'app.py' 文件在同一目录。")
#     print(f"原始错误: {e}")
#     raise e


# # --- 4. SCOW 配置 (V10 - 生产版) ---
# SCOW_CONFIG = {
#     'login_url': 'http://scow.saids.hpc.gleamoe.com/auth/public/auth?callbackUrl=http%3A%2F%2Fscow.saids.hpc.gleamoe.com%2Fapi%2Fauth%2Fcallback', 
#     'username_field': 'username',
#     'password_field': 'password',
#     'bill_url': 'http://scow.saids.hpc.gleamoe.com/mis/tenant/finance/bills', 
    
#     # (API 1 - 主列表)
#     'api_url_template': 'http://scow.saids.hpc.gleamoe.com/mis/api/finance/bill?page=1&pageSize=500&searchType=selfTenant&termStart={month}&termEnd={month}&type=1',
    
#     # (API 2 - 详情列表)
#     'api_details_url_template': 'http://scow.saids.hpc.gleamoe.com/mis/api/finance/userBill?accountBillIds={id}&accountName={username}',
    
#     'username': 'scowadmin',
#     'password': 'Saids_scowadmin'
# }


# # --- 5. 核心功能函数 ---

# def fetch_scow_bills(session, billing_month):
#     """
#     [V10 - 生产版]
#     1. 调用 API 1 获取主列表。
#     2. 循环，调用 API 2 获取详情。
#     3. 解析所有数据并返回。
#     """
#     try:
#         api_url_template = SCOW_CONFIG['api_url_template']
#         main_api_url = api_url_template.format(month=billing_month)
#         api_details_template = SCOW_CONFIG['api_details_url_template']
#     except KeyError as e:
#         click.echo(f"错误: SCOW_CONFIG 中缺少配置 {e}。", err=True)
#         return None, None
        
#     click.echo(f"--- 阶段 1: 正在调用主列表 API ---")
#     click.echo(f"API URL: {main_api_url}")

#     try:
#         response = session.get(main_api_url)
#         response.raise_for_status()
#         data = response.json()
#         bill_list = data.get('bills', [])
#         if not bill_list:
#             click.echo("API 1 成功，但未返回任何账单数据。")
#             return [], billing_month # 返回空列表，但成功

#         click.echo(f"API 1 成功，获取到 {len(bill_list)} 条总账单。")

#         # --- [!! V10 生产版解析 !!] ---
#         click.echo("\n--- 阶段 2: 开始抓取作业详情 (仅限有费用的用户) ---")
        
#         billing_data = [] # 最终返回的数据
        
#         for item in bill_list:
#             # 1. 计算总金额 (API 1)
#             yuan = item.get('amount', {}).get('yuan', 0)
#             decimal = item.get('amount', {}).get('decimalPlace', 0)
#             total_amount = Decimal(yuan) + (Decimal(decimal) / Decimal(10000))

#             # 2. 只处理有费用的用户
#             if total_amount > 0:
#                 username = item.get('accountName')
#                 bill_id = item.get('id')
                
#                 # 从 API 1 提取总作业费用
#                 job_cost_str = item.get('details', {}).get('作业费用', '0')
#                 job_cost_cleaned = re.sub(r'[^\d.]', '', job_cost_str)
#                 if not job_cost_cleaned: job_cost_cleaned = '0'
#                 total_job_cost = Decimal(job_cost_cleaned)

#                 if not username or not bill_id:
#                     click.echo(f"警告：跳过一条有费用的记录，缺少 'accountName' 或 'id'。")
#                     continue
                    
#                 click.echo(f"--- 正在为用户 [{username}] (Bill ID: {bill_id}) 抓取详情...")
                
#                 details_list = [] # 存储作业明细
                
#                 try:
#                     # 3. 构建并调用详情 API (API 2)
#                     details_url = api_details_template.format(id=bill_id, username=username) 
#                     details_response = session.get(details_url)
#                     details_response.raise_for_status()
#                     details_json = details_response.json()

#                     # 4. 解析详情 JSON (userBills)
#                     for user_bill in details_json.get('userBills', []):
#                         user_id = user_bill.get('userId')
                        
#                         # 使用 amount 对象计算金额
#                         detail_yuan = user_bill.get('amount', {}).get('yuan', 0)
#                         detail_decimal = user_bill.get('amount', {}).get('decimalPlace', 0)
#                         detail_cost = Decimal(detail_yuan) + (Decimal(detail_decimal) / Decimal(10000))
                        
#                         if user_id:
#                             details_list.append({
#                                 'userId': user_id,
#                                 'cost': detail_cost
#                             })
                    
#                     click.echo(f"成功抓取到 {len(details_list)} 条作业明细。")

#                 except requests.RequestException as e:
#                     click.echo(f"警告: 为用户 [{username}] 抓取详情失败: {e}", err=True)
#                     click.echo("邮件将只包含总金额，不含作业明细。")
#                 except Exception as e:
#                     click.echo(f"警告: 处理用户 [{username}] 的详情时出错: {e}", err=True)

#                 # 5. 将所有数据汇总
#                 billing_data.append({
#                     'username': username,
#                     'total_amount': total_amount,
#                     'job_cost': total_job_cost,
#                     'details': details_list # 包含作业明细
#                 })

#         click.echo("="*30)
#         click.echo(f"\n✅ 阶段 2 完成。共找到 {len(billing_data)} 个有费用的用户。")
        
#         return billing_data, billing_month

#     except requests.RequestException as e:
#         click.echo(f"API (主列表) 调用失败: {e}", err=True)
#         return None, None
#     except json.JSONDecodeError:
#         click.echo("错误：API (主列表) 响应不是有效的JSON。")
#         click.echo(f"原始响应: {response.text[:200]}...")
#         return None, None


# def get_email_from_ldap(ldap_conn, username):
#     """
#     [V4 - Flask Config 版] (保持不变)
#     """
#     try:
#         LDAP_BASE_DN = current_app.config['LDAP_BASE_DN']
#         LDAP_USER_OU = current_app.config.get('LDAP_USER_OU', 'ou=People')
#         LDAP_USER_SEARCH_BASE = f"{LDAP_USER_OU},{LDAP_BASE_DN}"
#     except KeyError as e:
#         click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
#         return None
#     search_filter = f'(uid={username})'
#     try:
#         ldap_conn.search(search_base=LDAP_USER_SEARCH_BASE, search_filter=search_filter, search_scope=SUBTREE, attributes=['mail'])
#         if ldap_conn.response and len(ldap_conn.response) > 0:
#             email = ldap_conn.response[0]['attributes'].get('mail')
#             if email: return email[0]
#         click.echo(f"LDAP中未找到用户 {username} 的邮箱 (mail) 属性。", err=True)
#         return None
#     except Exception as e:
#         click.echo(f"LDAP查询用户 {username} 时出错: {e}", err=True)
#         return None


# def send_billing_email(user_email, username, bill_data, billing_period):
#     """
#     [V10 - 生产版]
#     已升级，会动态生成“作业明细”表格。
#     """
#     try:
#         sender_email = current_app.config['MAIL_USERNAME']
#         sender_name = current_app.config.get('MAIL_SENDER_NAME', '算力平台管理员')
#     except KeyError as e:
#         click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
#         return

#     msg = Message(
#         subject=f"【HPC平台】{billing_period} 账期账单通知",
#         sender=f"{sender_name} <{sender_email}>",
#         recipients=[user_email]
#     )

#     login_url = SCOW_CONFIG['bill_url']
    
#     # --- [!! V10 动态生成详情表格 !!] ---
#     details_html = "" # 默认为空
    
#     # 检查 'details' 键是否存在，并且列表不为空
#     if 'details' in bill_data and bill_data['details']:
        
#         # 1. 开始构建表格
#         details_html = """
#         <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #10b981; padding-left: 10px;">
#             作业明细 (按用户ID)
#         </h3>
#         <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #ffffff; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
#             <thead style="background-color: #f8fafb;">
#                 <tr>
#                     <th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">用户ID</th>
#                     <th style="text-align: right; padding: 8px; border-bottom: 1px solid #ddd;">小计 (元)</th>
#                 </tr>
#             </thead>
#             <tbody>
#         """
        
#         # 2. 循环添加数据行 (复刻您的截图)
#         for detail_item in bill_data['details']:
#             details_html += f"""
#                 <tr>
#                     <td style="padding: 8px; border-bottom: 1px solid #e8e8e8;">{detail_item['userId']}</td>
#                     <td style="text-align: right; padding: 8px; border-bottom: 1px solid #e8e8e8;">{detail_item['cost']:.2f}</td>
#                 </tr>
#             """
            
#         # 3. 结束表格
#         details_html += "</tbody></table>"
#     # --- [!! 动态表格结束 !!] ---

    
#     # (V10 - 最终HTML模板)
#     msg.html = f"""
#     <!DOCTYPE html>
#     <html lang="zh-CN">
#     <head><meta charset="UTF-8"><title>账单通知</title></head>
#     <body style="margin: 0; padding: 0; background-color: #f4f7f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
#         <table width="100%" border="0" cellpadding="0" cellspacing="0" style="background-color: #f4f7f6;">
#             <tr>
#                 <td align="center" style="padding: 20px 0;">
#                     <table width="600" border="0" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
#                         <tr>
#                             <td align="center" style="padding: 30px 0; border-bottom: 1px solid #e8e8e8;">
#                                 <img src="cid:logo_image" alt="平台Logo" width="80" style="display: block;">
#                                 <h1 style="color: #0a2540; margin-top: 15px; font-size: 24px;">HPC 平台账单通知</h1>
#                             </td>
#                         </tr>
#                         <tr>
#                             <td style="padding: 30px 40px;">
#                                 <p style="color: #333; font-size: 16px; line-height: 1.6;">您好， {username}！</p>
#                                 <p style="color: #333; font-size: 16px; line-height: 1.6;">您在 {billing_period} 账期的账单已生成。明细如下：</p>
                                
#                                 <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #134790; padding-left: 10px;">
#                                     账单总览
#                                 </h3>
#                                 <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
#                                     <tr><td width="80" style="color: #555;">账  期:</td><td style="color: #111;">{billing_period}</td></tr>
#                                     <tr><td width="80" style="color: #555;">账户名:</td><td style="color: #111;">{bill_data['username']}</td></tr>
#                                     <tr><td style="color: #555;">作业费用:</td><td style="color: #111;">{bill_data['job_cost']:.2f}</td></tr>
#                                     <tr><td style="color: #555;"><b>总金额:</b></td><td style="color: #d9534f; font-weight: bold;">{bill_data['total_amount']:.2f}</td></tr>
#                                 </table>
                                
#                                 {details_html}
#                                 <table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding: 20px 0;">
#                                     <a href="{login_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;">登录平台查看</a>
#                                 </td></tr></table>
#                             </td>
#                         </tr>
#                         <tr>
#                             <td style="padding: 30px 40px; border-top: 1px solid #e8e8e8; text-align: center; color: #999; font-size: 12px;">
#                                 <p>此邮件为系统自动发送，请勿回复。</p>
#                                 <p>人工智能与数据科学学院算力中心</p>
#                             </td>
#                         </tr>
#                     </table>
#                 </td>
#             </tr>
#         </table>
#     </body>
#     </html>
#     """
    
#     # --- 嵌入 Logo 图片 ---
#     try:
#         logo_path = 'static/images/logo.png' 
#         with current_app.open_resource(logo_path, 'rb') as fp:
#             msg.attach(filename='logo.png', content_type='image/png', data=fp.read(), disposition='inline', headers={'Content-ID': '<logo_image>'})
#     except Exception as e:
#         click.echo(f"警告: 无法嵌入Logo图片 '{logo_path}': {e}", err=True)
#     # --- 发送邮件 ---
#     try:
#         mail.send(msg)
#         click.echo(f"已成功发送账单邮件至 {username} ({user_email})")
#     except Exception as e:
#         click.echo(f"发送邮件至 {username} ({user_email}) 失败: {e}", err=True)


# # --- 6. 注册为 Flask CLI 命令 (V10 - 生产版) ---

# @app.cli.command("send-bills")
# @click.option('--test-user', default=None, help='指定一个测试用户名 (如 ljt1) 进行全流程测试。')
# @click.option('--month', default=None, help='指定账期 (格式: YYYYMM, 例如: 202509)。默认为上个月。')
# def send_bills_command(test_user, month):
#     """
#     登录SCOW, 调用API抓取账单, 并通过LDAP查询邮箱, 最后发送账单。
#     """
#     click.echo("--- 开始执行账单通知任务 (V10 - 生产版) ---")

#     # 1. 登录 SCOW
#     session = requests.Session()
#     login_url = SCOW_CONFIG['login_url']
#     try:
#         click.echo(f"正在访问 (GET) {login_url} 以获取 cookies...")
#         get_resp = session.get(login_url)
#         get_resp.raise_for_status()
#     except requests.RequestException as e:
#         click.echo(f"访问 (GET) 登录页面失败: {e}", err=True)
#         return
#     try:
#         click.echo("正在解析登录表单以构建动态 payload...")
#         soup = BeautifulSoup(get_resp.text, 'html.parser')
#         login_form = soup.find('form', id='loginForm')
#         if not login_form:
#             click.echo("错误: 未能在登录页面上找到 id='loginForm' 的表单。", err=True)
#             return
#         inputs = login_form.find_all('input')
#         login_payload = {}
#         for input_tag in inputs:
#             name = input_tag.get('name')
#             value = input_tag.get('value', '')
#             if name: login_payload[name] = value
#         login_payload[SCOW_CONFIG['username_field']] = SCOW_CONFIG['username']
#         login_payload[SCOW_CONFIG['password_field']] = SCOW_CONFIG['password']
#         debug_payload = {k: v for k, v in login_payload.items() if k != SCOW_CONFIG['password_field']}
#         click.echo(f"动态 Payload 构建完成: {debug_payload}")
#     except Exception as e:
#         click.echo(f"解析登录表单时出错: {e}", err=True)
#         return
#     try:
#         click.echo(f"正在登录 (POST) {login_url} ...")
#         login_resp = session.post(login_url, data=login_payload)
#         login_resp.raise_for_status()
#         if "账号密码登录" in login_resp.text:
#              click.echo("登录失败！服务器返回了登录页面。", err=True)
#              click.echo("请立即核对 SCOW_CONFIG 中的 'username' 和 'password'。", err=True)
#              return
#         click.echo("登录 SCOW 成功。")
#     except requests.RequestException as e:
#         click.echo(f"登录 (POST) SCOW 失败: {e}", err=True)
#         return

#     # 2. 确定账期
#     if month:
#         billing_month_str = month
#         click.echo(f"使用指定的账期: {billing_month_str}")
#     else:
#         today = date.today()
#         first_day_current = today.replace(day=1)
#         last_day_prev = first_day_current - timedelta(days=1)
#         billing_month_str = last_day_prev.strftime('%Y%m')
#         click.echo(f"未指定账期，自动使用上个月: {billing_month_str}")

#     # 3. 抓取账单 (调用V10 生产版)
#     bills, billing_period = fetch_scow_bills(session, billing_month_str)
    
#     # [!! V10] 检查 fetch 是否失败
#     if bills is None:
#         click.echo("获取账单失败。任务终止。")
#         return
    
#     # 4. [!! V10 测试用户注入逻辑 (已更新) !!]
#     if test_user:
#         click.echo(f"\n--- 启动 {test_user} 全流程测试 ---")
#         ljt1_found = any(bill['username'] == test_user for bill in bills)
#         if not ljt1_found:
#             click.echo(f"用户 {test_user} 的费用为0或未在账单中。正在手动注入...")
#             bills.append({ 
#                 'username': test_user, 
#                 'total_amount': Decimal('0.00'), 
#                 'job_cost': Decimal('0.00'), 
#                 'details': [] # 注入一个空的详情列表
#             })
#             click.echo(f"已成功注入 {test_user} (0元账单)。")
    
#     if not bills:
#         click.echo("没有需要通知的用户（费用>0），任务完成。")
#         return

#     # 5. 连接 LDAP
#     try:
#         LDAP_SERVER_URI = current_app.config['LDAP_SERVER']
#         LDAP_BIND_DN = current_app.config['LDAP_BIND_DN']
#         LDAP_BIND_PASSWORD = current_app.config['LDAP_BIND_PASSWORD']
#         server = Server(LDAP_SERVER_URI, get_info=ALL)
#         ldap_conn = Connection(server, LDAP_BIND_DN, LDAP_BIND_PASSWORD, auto_bind=True)
#         click.echo("连接 LDAP 服务器成功。")
#     except Exception as e:
#         click.echo(f"连接 LDAP 失败: {e}", err=True)
#         return

#     # 6. 迭代处理并发送邮件
#     click.echo("\n--- 开始处理账单并发送邮件 ---")
#     for bill in bills:
#         username = bill['username']
#         click.echo(f"\n正在处理用户: {username}")
#         email = get_email_from_ldap(ldap_conn, username)
#         if email:
#             # (调用 V10 邮件函数)
#             send_billing_email(email, username, bill, billing_period)
#         else:
#             click.echo(f"跳过用户 {username} (未找到邮箱)。")

#     # 7. 清理
#     ldap_conn.unbind()
#     click.echo("\n--- 所有任务已完成 ---")

















































# #!/usr/bin/env python3
# # -*- coding: utf-8 -*-

# # --- 1. Python 标准库和第三方库 ---
# import requests 
# import re
# import json
# from decimal import Decimal
# from datetime import datetime, timedelta, date
# from bs4 import BeautifulSoup
# from ldap3 import Server, Connection, ALL, SUBTREE

# # --- 2. Flask 和应用相关的 imports ---
# import click
# from flask import current_app
# from flask_mail import Message

# # --- 3. 从您的主 'app.py' 导入 app 和 mail 实例 ---
# # (这必须在 @app.cli.command 之前)
# try:
#     from app import app, mail
# except ImportError as e:
#     print("CRITICAL ERROR: 无法从 'app' 导入 'app' 和 'mail'。")
#     print("请确保此文件与您的主 'app.py' 文件在同一目录。")
#     print(f"原始错误: {e}")
#     # 退出或引发错误，防止 Flask 进一步加载
#     raise e


# # --- 4. SCOW 配置 (V7 - API 修复版) ---
# SCOW_CONFIG = {
#     'login_url': 'http://scow.saids.hpc.gleamoe.com/auth/public/auth?callbackUrl=http%3A%2F%2Fscow.saids.hpc.gleamoe.com%2Fapi%2Fauth%2Fcallback', 
#     'username_field': 'username',
#     'password_field': 'password',
#     'bill_url': 'http://scow.saids.hpc.gleamoe.com/mis/tenant/finance/bills', 
    
#     # --- [!! 唯一的修改在这里 !!] ---
#     # (我们在末尾添加了 &type=1)
#     'api_url_template': 'http://scow.saids.hpc.gleamoe.com/mis/api/finance/bill?page=1&pageSize=500&searchType=selfTenant&termStart={month}&termEnd={month}&type=1',
#     # --- [!! 修改结束 !!] ---
    
#     'username': 'scowadmin',
#     'password': 'Saids_scowadmin'
# }


# # --- 5. 核心功能函数 ---

# def fetch_scow_bills(session, billing_month):
#     """
#     [V6 - API/JSON 调试模式]
#     直接调用内部 API 来获取账单数据 (JSON)。
#     它的工作是 *打印* JSON 并返回 None 来停止脚本。
#     """
#     # 从 SCOW_CONFIG 获取模板
#     try:
#         api_url_template = SCOW_CONFIG['api_url_template']
#         api_url = api_url_template.format(month=billing_month)
#     except KeyError:
#         click.echo("错误: SCOW_CONFIG 中未找到 'api_url_template'。", err=True)
#         return None, None
        
#     click.echo(f"HTML解析策略已放弃 (AJAX)。正在尝试调用内部 API...")
#     click.echo(f"API URL: {api_url}")

#     try:
#         # 使用已登录的 session 调用 API
#         response = session.get(api_url)
#         response.raise_for_status() # 确保请求成功
        
#         # --- [!! 这就是您要的“测试”和“打印” !!] ---
#         click.echo("="*30)
#         click.echo(f"--- API 响应 (Raw JSON) - 账期 {billing_month} ---")
#         click.echo(response.text) # 打印原始JSON文本
#         click.echo("--- API 响应结束 ---")
#         click.echo("="*30)
        
#         click.echo("\n✅ API 调用成功！")
#         click.echo("下一步：请将上面 '---' 之间的 JSON 文本复制并回复给我。")

#     except requests.RequestException as e:
#         click.echo(f"API 调用失败: {e}", err=True)
#         return None, None
#     except Exception as e:
#         click.echo(f"处理API响应时出错: {e}", err=True)
#         return None, None

#     # (在调试模式下，我们不返回数据，只打印)
#     return None, billing_month


# def get_email_from_ldap(ldap_conn, username):
#     """
#     [V4 - Flask Config 版]
#     使用给定的用户名 (uid) 在 LDAP 中查询邮箱地址。
#     """
#     try:
#         # 从 Flask Config 读取 LDAP 配置
#         LDAP_BASE_DN = current_app.config['LDAP_BASE_DN']
#         LDAP_USER_OU = current_app.config.get('LDAP_USER_OU', 'ou=People')
#         LDAP_USER_SEARCH_BASE = f"{LDAP_USER_OU},{LDAP_BASE_DN}"
#     except KeyError as e:
#         click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
#         return None

#     search_filter = f'(uid={username})'
#     try:
#         ldap_conn.search(
#             search_base=LDAP_USER_SEARCH_BASE,
#             search_filter=search_filter,
#             search_scope=SUBTREE,
#             attributes=['mail']
#         )
        
#         if ldap_conn.response and len(ldap_conn.response) > 0:
#             email = ldap_conn.response[0]['attributes'].get('mail')
#             if email:
#                 return email[0]
        
#         click.echo(f"LDAP中未找到用户 {username} 的邮箱 (mail) 属性。", err=True)
#         return None
        
#     except Exception as e:
#         click.echo(f"LDAP查询用户 {username} 时出错: {e}", err=True)
#         return None


# def send_billing_email(user_email, username, bill_data, billing_period):
#     """
#     [V5 - 修复版] 向单个用户发送HTML账单邮件。
#     (移除了 'job_cost'，因为表头中不存在)
#     """
#     # 从 Flask Config 读取发件人
#     try:
#         sender_email = current_app.config['MAIL_USERNAME']
#         sender_name = current_app.config.get('MAIL_SENDER_NAME', '算力平台管理员')
#     except KeyError as e:
#         click.echo(f"错误：您的 .env/Config 文件中缺少 {e} 配置。", err=True)
#         return

#     msg = Message(
#         subject=f"【HPC平台】{billing_period} 账期账单通知",
#         sender=f"{sender_name} <{sender_email}>",
#         recipients=[user_email]
#     )

#     login_url = SCOW_CONFIG['bill_url']
    
#     # (V5 - 修复版HTML模板)
#     msg.html = f"""
#     <!DOCTYPE html>
#     <html lang="zh-CN">
#     <head><meta charset="UTF-8"><title>账单通知</title></head>
#     <body style="margin: 0; padding: 0; background-color: #f4f7f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
#         <table width="100%" border="0" cellpadding="0" cellspacing="0" style="background-color: #f4f7f6;">
#             <tr>
#                 <td align="center" style="padding: 20px 0;">
#                     <table width="600" border="0" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
#                         <tr>
#                             <td align="center" style="padding: 30px 0; border-bottom: 1px solid #e8e8e8;">
#                                 <img src="cid:logo_image" alt="平台Logo" width="80" style="display: block;">
#                                 <h1 style="color: #0a2540; margin-top: 15px; font-size: 24px;">HPC 平台账单通知</h1>
#                             </td>
#                         </tr>
#                         <tr>
#                             <td style="padding: 30px 40px;">
#                                 <p style="color: #333; font-size: 16px; line-height: 1.6;">您好， {username}！</p>
#                                 <p style="color: #333; font-size: 16px; line-height: 1.6;">您在 {billing_period} 账期的账单已生成。明细如下：</p>
                                
#                                 <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #134790; padding-left: 10px;">账单详情</h3>
#                                 <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
#                                     <tr><td width="80" style="color: #555;">账  期:</td><td style="color: #111;">{billing_period}</td></tr>
#                                     <tr><td width="80" style="color: #555;">账户名:</td><td style="color: #111;">{bill_data['username']}</td></tr>
#                                     <tr><td style="color: #555;"><b>总金额:</b></td><td style="color: #d9534f; font-weight: bold;">{bill_data['total_amount']:.2f}</td></tr>
#                                 </table>
                                
#                                 <table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding: 20px 0;">
#                                     <a href="{login_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;">查看账单详情</a>
#                                 </td></tr></table>
#                             </td>
#                         </tr>
#                         <tr>
#                             <td style="padding: 30px 40px; border-top: 1px solid #e8e8e8; text-align: center; color: #999; font-size: 12px;">
#                                 <p>此邮件为系统自动发送，请勿回复。</p>
#                                 <p>人工智能与数据科学学院算力中心</p>
#                             </td>
#                         </tr>
#                     </table>
#                 </td>
#             </tr>
#         </table>
#     </body>
#     </html>
#     """

#     # --- 嵌入 Logo 图片 ---
#     try:
#         logo_path = 'static/images/logo.png' 
#         with current_app.open_resource(logo_path, 'rb') as fp:
#             msg.attach(
#                 filename='logo.png', content_type='image/png', data=fp.read(),
#                 disposition='inline', headers={'Content-ID': '<logo_image>'}
#             )
#     except Exception as e:
#         click.echo(f"警告: 无法嵌入Logo图片 '{logo_path}': {e}", err=True)

#     # --- 发送邮件 (使用 Flask-Mail) ---
#     try:
#         mail.send(msg)
#         click.echo(f"已成功发送账单邮件至 {username} ({user_email})")
#     except Exception as e:
#         click.echo(f"发送邮件至 {username} ({user_email}) 失败: {e}", err=True)


# # --- 6. 注册为 Flask CLI 命令 (V6 - API版) ---
# # [!! 这个版本包含了 --month 选项 !!]

# @app.cli.command("send-bills")
# @click.option('--test-user', default=None, help='指定一个测试用户名 (如 ljt1) 进行全流程测试。')
# @click.option('--month', default=None, help='指定账期 (格式: YYYYMM, 例如: 202509)。默认为上个月。')
# def send_bills_command(test_user, month):
#     """
#     登录SCOW, 调用API抓取账单, 并通过LDAP查询邮箱, 最后发送账单。
#     """
#     click.echo("--- 开始执行账单通知任务 (V6 - API 模式) ---")

#     # 1. 登录 SCOW (V5 动态 payload 逻辑)
#     session = requests.Session()
#     login_url = SCOW_CONFIG['login_url']
    
#     try:
#         click.echo(f"正在访问 (GET) {login_url} 以获取 cookies...")
#         get_resp = session.get(login_url)
#         get_resp.raise_for_status()
#     except requests.RequestException as e:
#         click.echo(f"访问 (GET) 登录页面失败: {e}", err=True)
#         return
    
#     try:
#         click.echo("正在解析登录表单以构建动态 payload...")
#         soup = BeautifulSoup(get_resp.text, 'html.parser')
#         login_form = soup.find('form', id='loginForm')
#         if not login_form:
#             click.echo("错误: 未能在登录页面上找到 id='loginForm' 的表单。", err=True)
#             return

#         inputs = login_form.find_all('input')
#         login_payload = {}
#         for input_tag in inputs:
#             name = input_tag.get('name')
#             value = input_tag.get('value', '')
#             if name:
#                 login_payload[name] = value
        
#         login_payload[SCOW_CONFIG['username_field']] = SCOW_CONFIG['username']
#         login_payload[SCOW_CONFIG['password_field']] = SCOW_CONFIG['password']
        
#         debug_payload = {k: v for k, v in login_payload.items() if k != SCOW_CONFIG['password_field']}
#         click.echo(f"动态 Payload 构建完成: {debug_payload}")
        
#     except Exception as e:
#         click.echo(f"解析登录表单时出错: {e}", err=True)
#         return

#     try:
#         click.echo(f"正在登录 (POST) {login_url} ...")
#         login_resp = session.post(login_url, data=login_payload)
#         login_resp.raise_for_status()
        
#         if "账号密码登录" in login_resp.text:
#              click.echo("登录失败！服务器返回了登录页面。", err=True)
#              click.echo("请立即核对 SCOW_CONFIG 中的 'username' 和 'password'。", err=True)
#              return
        
#         click.echo("登录 SCOW 成功。")

#     except requests.RequestException as e:
#         click.echo(f"登录 (POST) SCOW 失败: {e}", err=True)
#         return

#     # 2. 确定账期 (新逻辑)
#     if month:
#         billing_month_str = month
#         click.echo(f"使用指定的账期: {billing_month_str}")
#     else:
#         # 自动计算上个月
#         today = date.today()
#         first_day_current = today.replace(day=1)
#         last_day_prev = first_day_current - timedelta(days=1)
#         billing_month_str = last_day_prev.strftime('%Y%m')
#         click.echo(f"未指定账期，自动使用上个月: {billing_month_str}")

#     # 3. 抓取账单 (调用V6 API版本)
#     # (在V6调试模式下, bills 会是 None)
#     bills, billing_period = fetch_scow_bills(session, billing_month_str)
    
#     # [!! 调试模式] 脚本将在 fetch_scow_bills 中停止 (因为返回 None)，这是预期的。
#     if bills is None:
#         click.echo("调试模式：脚本已暂停，等待您提供JSON数据。")
#         return # 退出
    
#     # (--- 以下是拿到JSON后才会运行的代码 ---)

#     # 4. [!! 测试用户注入逻辑 !!]
#     if test_user:
#         click.echo(f"\n--- 启动 {test_user} 全流程测试 ---")
#         ljt1_found = any(bill['username'] == test_user for bill in bills)
        
#         if not ljt1_found:
#             click.echo(f"用户 {test_user} 的费用为0或未在账单中。正在手动注入...")
            
#             bills.append({
#                 'username': test_user,
#                 'total_amount': Decimal('0.00'),
#                 'job_cost': Decimal('0.00')
#             })
#             click.echo(f"已成功注入 {test_user} (0元账单)。")
#         else:
#             click.echo(f"用户 {test_user} 已在账单列表中(费用>0)，将按正常流程处理。")

#     if not bills:
#         click.echo("没有需要通知的用户，任务完成。")
#         return

#     # 5. 连接 LDAP
#     try:
#         LDAP_SERVER_URI = current_app.config['LDAP_SERVER']
#         LDAP_BIND_DN = current_app.config['LDAP_BIND_DN']
#         LDAP_BIND_PASSWORD = current_app.config['LDAP_BIND_PASSWORD']
#         server = Server(LDAP_SERVER_URI, get_info=ALL)
#         ldap_conn = Connection(server, 
#                              LDAP_BIND_DN, 
#                              LDAP_BIND_PASSWORD, 
#                              auto_bind=True)
#         click.echo("连接 LDAP 服务器成功。")
#     except Exception as e:
#         click.echo(f"连接 LDAP 失败: {e}", err=True)
#         return

#     # 6. 迭代处理并发送邮件
#     click.echo("\n--- 开始处理账单并发送邮件 ---")
#     for bill in bills:
#         username = bill['username']
#         click.echo(f"\n正在处理用户: {username}")
#         email = get_email_from_ldap(ldap_conn, username)
#         if email:
#             send_billing_email(email, username, bill, billing_period)
#         else:
#             click.echo(f"跳过用户 {username} (未找到邮箱)。")

#     # 7. 清理
#     ldap_conn.unbind()
#     click.echo("\n--- 所有任务已完成 ---")













